package com.freewater.parking.domain.parking;

import com.freewater.parking.domain.common.BaseAggregateRoot;
import com.freewater.parking.domain.common.EventQueue;
import com.freewater.parking.domain.common.PlateNumber;
import org.apache.commons.lang3.Validate;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 停车聚合
 */
public class Parking extends BaseAggregateRoot {
    /**
     * 车牌号
     */
    private final PlateNumber plateNumber;
    /**
     * 入场时间
     */
    private LocalDateTime checkInTime;
    /**
     * 总支付金额
     */
    private Integer totalPaid;
    /**
     * 最后一次支付时间
     */
    private LocalDateTime lastPayTime;

    /**
     * 工厂：用车牌号实例化一个停车实例
     *
     * @param plateNumber 车牌号
     */
    public Parking(PlateNumber plateNumber) {
        Validate.notNull(plateNumber, "车牌号不能为空");
        this.plateNumber = plateNumber;
        this.totalPaid = 0;
    }

    /**
     * 给JPA使用
     */
    public Parking(PlateNumber plateNumber,
                   LocalDateTime checkInTime,
                   Integer totalPaid,
                   LocalDateTime lastPayTime) {
        this.plateNumber = plateNumber;
        this.checkInTime = checkInTime;
        this.totalPaid = totalPaid;
        this.lastPayTime = lastPayTime;
    }

    @Override
    public Serializable getId() {
        return this.plateNumber;
    }

    /**
     * 入场：如果已入场，则抛出重复入场异常
     *
     * @param checkInTime 入场时间
     */
    public boolean checkIn(EventQueue queue, LocalDateTime checkInTime) {
        if (isInPark()) {
            queue.enqueue(new CheckInFailedEvent(this.plateNumber, checkInTime, "重复入场"));
            return false;
        }
        this.checkInTime = checkInTime;
        queue.enqueue(new CheckedInEvent(this.plateNumber, checkInTime));
        return true;
    }

    /**
     * 支付停车费：可分多次支付，如果支付总金额超过应付总金额抛出异常
     *
     * @param parkingFee 总共需要支付的停车费
     * @param payFee     本次支付的费用
     * @param payTime    本次支付时间
     * @throws NotCheckedInException 如果未入场
     * @throws NoNeedPayException    如果支付总金额超过应付总金额或者费用为0
     */
    public void payParkingFee(EventQueue queue,
                              int parkingFee,
                              int payFee,
                              LocalDateTime payTime) throws
            NotCheckedInException, NoNeedPayException {
        if (!isInPark()) {
            throw new NotCheckedInException("车辆不在场");
        }
        if (isCloseExpense(parkingFee)) {
            throw new NoNeedPayException("无需付费或者已结清费用");
        }
        this.totalPaid += payFee;
        this.lastPayTime = payTime;
        queue.enqueue(new PaidEvent(this.plateNumber, payTime, payFee, this.checkInTime));
    }

    /**
     * 出场：必须已入场，并且费用结清
     *
     * @param queue        事件队列
     * @param parkingFee   总共需要支付的停车费
     * @param checkOutTime 离场时间
     */
    public boolean checkOut(EventQueue queue,
                            int parkingFee,
                            LocalDateTime checkOutTime) {
        if (!isInPark()) {
            queue.enqueue(new CheckOutFailedEvent(this.plateNumber, checkOutTime, "车辆不在场"));
            return false;
        }
        if (!isCloseExpense(parkingFee)) {
            queue.enqueue(new CheckOutFailedEvent(this.plateNumber, checkOutTime, "未结清费用"));
            return false;
        }
        queue.enqueue(new CheckedOutEvent(this.plateNumber, checkOutTime, this.checkInTime));
        this.checkInTime = null;
        this.totalPaid = 0;
        this.lastPayTime = null;
        return true;
    }

    /**
     * 获取入场时间
     *
     * @return 入场时间
     */
    public LocalDateTime getCheckInTime() {
        return checkInTime;
    }

    /**
     * 获取车牌号
     *
     * @return 车牌号
     */
    public PlateNumber getPlateNumber() {
        return plateNumber;
    }

    /**
     * 获取累计付费金额
     *
     * @return 累计付费金额
     */
    public Integer getTotalPaid() {
        return totalPaid;
    }

    /**
     * 获取最后一次支付时间
     *
     * @return 最后一次支付时间
     */
    public LocalDateTime getLastPayTime() {
        return lastPayTime;
    }

    /**
     * 是否已入场
     *
     * @return 是否已入场
     */
    public boolean isInPark() {
        return this.checkInTime != null;
    }

    /**
     * 计算未支付的停车费
     *
     * @param parkingFee 总共需要支付的停车费
     * @return 未支付的停车费
     */
    public int calcUnPaidFee(int parkingFee) {
        return Math.max(parkingFee - this.totalPaid, 0);
    }

    /**
     * 是否已结清停车费
     *
     * @param parkingFee 总共需要支付的停车费
     * @return 是否已结清停车费
     */
    public boolean isCloseExpense(int parkingFee) {
        return calcUnPaidFee(parkingFee) <= 0;
    }

    @Override
    public String toString() {
        return "Parking{" +
                "plateNumber=" + plateNumber +
                ", checkInTime=" + checkInTime +
                ", totalPaid=" + totalPaid +
                ", lastPayTime=" + lastPayTime +
                '}';
    }
}
